#include <iostream>
#include <stdint.h>
#include <cstdlib>
#include <cctype>
#include <string>
#include <vector>
#include <algorithm>

#include "parse.h"
#include "error.h"
#include "mem.h"
#include "cmd.h"

using namespace std;

/* Helper functions for parsing the source code */
// Get the most basic unit of an expression
static double getExprUnit(unsigned short int& idx, const vector<string>& tokens, int& nLine);

// Get a factor of a term of an expression
static double getFactor(unsigned short int& idx, const vector<string>& tokens, int& nLine);

// Break the source code into tokens
static vector<string> tokenize(const string& srcLine, int nLine);

// Return 'true' if the character is an operator. 'numToken' is passed to classify the decimal point.
static bool isOperator(char charToken, bool numToken);

// Return 'true' if the character is alphanumeric or '_' or '@'
static bool isLiteral(char charToken);

// Return 'true' if the character is a delimiter (whitespace)
static bool isDelim(char charToken);

// Returns 'true' if the string passed is a valid number
static bool isValidNum(const string& num);

static bool firstRun = true;        // Holds true if the interpreter is being run for the 1st time

std::vector<std::string> cmdList;   // Buffer to hold keywords

std::vector<cmdPtr> keyWrdList;     // List of pointers to 'keyword-handlers'

short int bracketBalance = 0;   // 0 if there is no mismatch of brackets

static void init_nbXdy()
{
    /* Load the keywords into memory */
    cmdList.push_back("var");
    cmdList.push_back("delete");
    cmdList.push_back("exit");
    cmdList.push_back("print");
    cmdList.push_back("let");

    /* Load corresponding call-back functions (keyword-handlers) into memory */
    keyWrdList.push_back(varIs);
    keyWrdList.push_back(deleteVar);
    keyWrdList.push_back(exitPrg);
    keyWrdList.push_back(print);
    keyWrdList.push_back(let);
}

void parse(string& srcLine, int& nLine)
{
    /**
     *  This function parses one line of code and executes it
     *  Input:
     *      A reference to a string type variable holding the line of code
     *
     *  Output:
     *      Runs the code, returns nothing
     */
    /* Check if this is the first run of the interpreter. If so, initialize it. */
    if(firstRun)
    {
        init_nbXdy();
        firstRun = false;
    }

    /* Get the number of tokens and store the tokens in a buffer */
    vector<string> tokens = tokenize(srcLine, nLine);
    unsigned int nTokens = tokens.size();

    /* Following are to iterate through the list of keywords */
    unsigned short int idxCmd;
    vector<string>::iterator itrCmd;

    /* For every keyword available, execute the corresponding callback function */
    for(unsigned short int idx=0; idx<nTokens; idx++)
    {
        /* Find the index of the keyword in the list of available keywords */
        idxCmd = (itrCmd = find(cmdList.begin(), cmdList.end(), tokens.at(idx))) - cmdList.begin();

        /* If the keyword wasn't found in the list display an error message */
        if(itrCmd == cmdList.end())// && isVarExist(tokens.at(idx)) == NOT_FOUND)
            errorMessage(ERR_INVALID_KEYWORD, tokens.at(idx), nLine);

        /* //Following code is for assignment of variables
        else if(isVarExist(tokens.at(idx)) != NOT_FOUND)
        {
            if(tokens.size() - idx < 3)
                errorMessage(ERR_INVALID_TOKEN_COUNT, "foo", nLine);

            // Check for the arrow-assignment operator
            if(tokens.at(idx+1) == "-" && tokens.at(idx+2) == ">")
            {
                clog<<"Code to init var = "<<tokens.at(idx+3)<<endl;
                idx += 3;
            }
            else
                errorMessage(ERR_CUSTOM, "The assignment operator \'->\' was expected here.", nLine);
        }//*/
        else
            (*keyWrdList.at(idxCmd))(idx, tokens, nLine);
    }
}

double getExpr(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    double value = 0.0;
    /**
     *  The expression parser evaluates expressions involving numbers and number variables. It uses
     *  the Recursive Descent Parsing algorithm. This forms an important portion of the interpreter as
     *  many other statements are dependent on this function.
     *  Input:
     *      A list of available tokens in the current line of source code
     *      Index of the token being processed
     *      The line number of the source being processed
     *
     *  Output:
     *      The value of the expression
     *
     *  Expression :=
     *      AddOp | SubOp | Term
     *
     *  Term :=
     *      MulOp | DivOp | Factor
     *
     *  Factor :=
     *      Number | Identifier | Expression
     */

    /* While there are tokens still remaining in the line or till we encounter a semicolon */
    while(idx < tokens.size() && tokens.at(idx) != ";" && tokens.at(idx) != ",")
    {
        if(tokens.at(idx) == "+")
            value += getFactor(++idx, tokens, nLine);

        else if(tokens.at(idx) == "-")
            value -= getFactor(++idx, tokens, nLine);

        else if(tokens.at(idx) == ")")
        {
            bracketBalance--;
            idx++;
            break;
        }

        else value = getFactor(idx, tokens, nLine);
    }

    /* Check for balance of brackets */
    if(bracketBalance < 0)
        errorMessage(ERR_EXPECTED_SYMBOL, "(", nLine);

    /* Check for closing brackets only in the "outermost" expression. This is done where 'getExpr' is called */

    else
        return value;

    return 0.0; /* To stop warnings about control reaching end of non-void function */
}

static double getFactor(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    /**
     *  The function evaluates the value of the term.
     *  Input:
     *      A list of available tokens in the current line of source code
     *      Index of the token being processed
     *      The line number of the source being processed
     *
     *  Output:
     *      The value of the term
     */

    double value = 0.0;

    /* Keep taking in tokens till we encounter EOL or semicolon */
    while(idx < tokens.size() && tokens.at(idx) != ";" && tokens.at(idx) != ",")
    {
        if(tokens.at(idx) == "*")
            value *= getExprUnit(++idx, tokens, nLine);

        else if(tokens.at(idx) == "/")
            value /= getExprUnit(++idx, tokens, nLine);

        else if(tokens.at(idx) == "+" || tokens.at(idx) == "-" || tokens.at(idx) == ")")
            return value;

        else value = getExprUnit(idx, tokens, nLine);
    }

    return value;
}

static double getExprUnit(unsigned short int& idx, const vector<string>& tokens, int& nLine)
{
    /**
     *  This function lies at the lowest level of the RDP. It returns the value of the variable
     *  (number type) or the actual number or calls the getExpr(...) function for parsing nested
     *  expressions.
     *  Input:
     *      A list of available tokens in the current line of source code
     *      Index of the token being processed
     *      The line number of the source being processed
     *
     *  Output:
     *      The value of the unit
     */

    double value = 0.0;
    int varIdx = NOT_FOUND;

    /* If the token is a number type variable and it is initialized */
    if((varIdx = isVarExist(tokens.at(idx))) != NOT_FOUND && \
            getVarType(tokens.at(idx)) == VAR_NUM)
            {
                if(isVarInit(tokens.at(idx), nLine))
                    value = getNumData(tokens.at(idx++), nLine);
                else
                    errorMessage(ERR_IDENT_UNINIT, tokens.at(idx), nLine);
            }

    /* In case the variable does not exist (take care of other 'loose' ends as well) */
    else if(isVarExist(tokens.at(idx)) == NOT_FOUND && !isdigit(tokens.at(idx).at(0)) \
            && tokens.at(idx) != "(")
        errorMessage(ERR_INVALID_IDENT, tokens.at(idx), nLine);

    /* If the token is a number, the first character will be a number */
    else if(isValidNum(tokens.at(idx)))
        value = strtod(tokens.at(idx++).c_str(), NULL);

    /* If it is NOT a number; again take care of other cases in this condition */
    else if(!isValidNum(tokens.at(idx)) && tokens.at(idx) != "(")
        errorMessage(ERR_INVALID_NUMBER, tokens.at(idx), nLine);

    /* If opening brackets are encountered we have ourselves a nested expression */
    else if(tokens.at(idx) == "(")
    {
        bracketBalance++;
        if( (unsigned)(idx+1) < tokens.size())
            value = getExpr(++idx, tokens, nLine);
        else
            errorMessage(ERR_EXPECTED_EXPR, "foo", nLine);
    }
    else
        errorMessage(ERR_UNKOWN_TOKEN, tokens.at(idx), nLine);

    return value;
}

static string getQString(unsigned int& idx, const string& srcLine, int& nLine)
{
    /**
     *  This function will extract string data enclosed in quotes from the line of code.
     *  The function also makes sure that the string is ended with a quote.
     *  Input:
     *      A reference of string type variable holding the line of code, current position in the line
     *      and current line number in the source code
     *  Output:
     *      A string containing the quoted string data
     */

    /* Buffer to hold final string data */
    string strData;

    /* The index currently points to the character after the first quote */
    idx = (idx+1) < srcLine.size() ? ++idx : idx;

    /* Push in a marker to distinguish between quoted string data and keywords & literals */
    strData.push_back(0xFF);

    while(idx < srcLine.size() && srcLine.at(idx) != '\"')
        strData.push_back(srcLine.at(idx++));

    /*
        Check if the starting and ending quotes were matched. The index should point at the ending
        quote IF it exists
    */
    if(idx >= srcLine.size() || srcLine.at(idx) != '\"')
        errorMessage(ERR_EXPECTED_SYMBOL, "\"", nLine);

    /* Eat up the ending quote */
    idx++;

    return strData;
}

static vector<string> tokenize(const string& srcLine, int nLine)
{
    /**
     *  This function tokenizes a line of code. The token identification is hard-coded.
     *  Input:
     *      A reference to a string type variable containing the line of code
     *  Output:
     *      A list of tokens available in the line of code
     */

    vector<string> tokens;      // The final list of tokens
    string curToken;            // Current token being processed
    bool numToken = false;      // Flag to check if the current token is a number or a string literal

    unsigned int curIndex = 0;  // Index to the current character in the line of code

        while(curIndex < srcLine.length())
        {
            /* For single character operators */
            if(isOperator(srcLine[curIndex], numToken))
            {
                /* For comment */
                if(srcLine[curIndex] == '~')
                    curIndex = srcLine.length();

                /* Store the quoted string data as a token */
                else if(srcLine[curIndex] == '\"')
                    curToken = getQString(curIndex, srcLine, nLine);

                else/**/
                    curToken.push_back(srcLine[curIndex++]);
            }

            /* Ignore spaces, newlines and tabs */
            else if(isDelim(srcLine[curIndex]))
                curIndex++;

            /* If it is alphanumeric or '_' or '@' or '.' */
            else if(isLiteral(srcLine[curIndex]) || srcLine[curIndex] == '.')
            {
                /* If a token starts with a number, it is assumed to be a number-token */
                if(isdigit(srcLine[curIndex]))
                    numToken = true;

                /*
                    Keep adding characters until a delimiter or an operator is encountered or
                    the entire line has been read
                */
                while(!isDelim(srcLine[curIndex]) && !isOperator(srcLine[curIndex], numToken) && \
                        curIndex < srcLine.length())
                {
                    /*
                        If the first character is a digit, the token is assumed to be a number,
                        otherwise a string literal. A number followed immediately by alphabets or '_'
                        or '@' results in an error - invalid number.
                    */
                    if((numToken && !isdigit(srcLine[curIndex])) && (numToken && srcLine[curIndex] != '.'))
                        errorMessage(ERR_INVALID_NUMBER, curToken, nLine);
                    else
                        curToken.push_back(srcLine[curIndex++]);
                }

                /* Reset the flag for a number-token after the token has been stored */
                if(numToken) numToken = false;
            }
            else
                /* The token could not be identified at all */
                errorMessage(ERR_UNKOWN_TOKEN, curToken, nLine);
            /*
                Store the current token if it's not empty (in case of delimiters) and
                clear the buffer 'curToken'
            */
            if(!curToken.empty())
            {
                tokens.push_back(curToken);
                curToken.clear();
            }
        }
    return tokens;
}

static bool isOperator(char charToken, bool numToken)
{
    /* If the dot is found in the middle of a number, it is to be treated as a decimal point */
    if(numToken && charToken == '.')
        return false;

    /*
        If any of these characters are encountered, this function will return "true" ->
        []{}()+=-*!#$%^&|<>/?\'",.~:;
    */

    if (charToken == '[' || \
        charToken == ']' || \
        charToken == '{' || \
        charToken == '}' || \
        charToken == '(' || \
        charToken == ')' || \
        charToken == '+' || \
        charToken == '=' || \
        charToken == '-' || \
        charToken == '*' || \
        charToken == '!' || \
        charToken == '#' || \
        charToken == '$' || \
        charToken == '%' || \
        charToken == '^' || \
        charToken == '&' || \
        charToken == '|' || \
        charToken == '<' || \
        charToken == '>' || \
        charToken == '/' || \
        charToken == '?' || \
        charToken == '\\' || \
        charToken == '\'' || \
        charToken == '"' || \
        charToken == ',' || \
        charToken == '~' || \
        charToken == ':' || \
        charToken == ';' || \
        charToken == '.')
            return true;
    else
        return false;
}

static bool isLiteral(char charToken)
{
    /* Part of either a keyword or a string literal or a number */
    if(isalnum(charToken) || charToken == '_' || charToken == '@')
        return true;
    else
        return false;
}

static bool isDelim(char charToken)
{
    /* May demarcate the boundaries of a token */
    if (charToken == 0x20 || charToken == 0x0A || charToken == 0x09 || \
        charToken == 0x0D || charToken == 0x00)
        return true;
    else
        return false;
}

static bool isValidNum(const string& num)
{
    unsigned short int decPt = 0;
    unsigned int nDigits = 0;
    unsigned int i = 0;
    for(; i<num.length(); i++)
    {
        if(num.at(i) == '.') decPt++;
        if(num.at(i) == '.' && (i==0 || i==num.length()-1)) break;
        if((num.at(i) >= '0' && num.at(i) <= '9') || num.at(i) == '.') nDigits++; else break;
    }

    if((decPt == 0 || decPt == 1) && i >= num.length())
        return true;
    else
        return false;
}
